package com.horton.dto;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * JavaBean单元测试生成器
 * 2022-05-01
 * hortonwei@foxmail.com
 */
public class UnitTestGeneratorForBean {

    private static final Class<RequestDTO> clazz = RequestDTO.class;
    private static final StringBuilder packageBuilder = new StringBuilder();
    private static final StringBuilder importBuilder = new StringBuilder();
    private static final StringBuilder headerBuilder = new StringBuilder();
    private static final StringBuilder footerBuilder = new StringBuilder();
    private static final StringBuilder fieldBuilder = new StringBuilder();
    private static final StringBuilder beforeBuilder = new StringBuilder();
    private static final StringBuilder methodBuilder = new StringBuilder();
    private static final Package aPackage;
    private static final String clazzName;
    private static final Set<Method> methods = new HashSet<>();
    private static final String clazzNameCamel;

    static {
        aPackage = clazz.getPackage();
        clazzName = clazz.getSimpleName();
        clazzNameCamel = clazzName.substring(0, 1).toLowerCase(Locale.ROOT) + clazzName.substring(1);//小驼峰对象名
    }

    public static void main(String[] args) {
        init();
        UnitTestGeneratorForBean generator = new UnitTestGeneratorForBean();
        generator.buildTemplate();
        generator.writeToFile();
    }

    public static void init() {
        for (Method method : clazz.getMethods()) {
            String name = method.getName();
            if (name.equals("getClass")) {
                continue;
            }
            if (name.startsWith("get") || name.startsWith("set")) {
                methods.add(method);
            }
        }
    }

    public void buildTemplate() {
        packageBuilder.append("package ").append(aPackage.getName()).append(";\n");
        importBuilder.append("import mockit.Tested;\n")
                .append("import mockit.integration.junit4.JMockit;\n")
                .append("import org.junit.Before;\n")
                .append("import org.junit.Test;\n")
                .append("import org.junit.runner.RunWith;\n")
                .append("import static org.junit.Assert.*;\n");
        headerBuilder.append("public class ").append(clazzName).append("Test {\n");
        fieldBuilder.append("\t@Tested\n")
                .append("\tprivate final ").append(clazzName).append(" ").append(clazzNameCamel)
                .append(" = new ").append(clazzName).append("();\n");
        beforeBuilder.append("\t@Before\n")
                .append("\tpublic void before() throws Exception {\n");
        for (Method method : methods) {
            if (method.getName().startsWith("set")) {
                beforeBuilder.append("\t\t").append(clazzNameCamel).append(".").append(method.getName()).append("(");
                Parameter[] parameters = method.getParameters();
                switchParameters(parameters, beforeBuilder);
                beforeBuilder.append(");\n");
            }
        }
        beforeBuilder.append("\t}");
        methodBuilder.append("\n");
        for (Method method : methods) {
            if (method.getName().startsWith("get")) {
                methodBuilder.append("\t@Test\n")
                        .append("\tpublic void ").append(method.getName()).append("() {").append("\n");
                methodBuilder.append("\t\t");
                String returnTypeSimpleName = method.getReturnType().getSimpleName();
                switchReturnType(returnTypeSimpleName, method.getName());
                methodBuilder.append("\t}\n");
            }
        }
        footerBuilder.append("}");
        importBuilder.append("@RunWith(JMockit.class)\n");
    }

    public static void switchParameters(Parameter[] parameters, StringBuilder builder) {
        for (Parameter parameter : parameters) {
            String simpleName = parameter.getType().getSimpleName();
            switch (simpleName) {
                case "String":
                    builder.append("\"str\"");
                    break;
                case "int":
                case "byte":
                case "short":
                    builder.append("1");
                    break;
                case "long":
                    builder.append("1L");
                    break;
                case "double":
                case "float":
                    builder.append("1.0");
                    break;
                case "char":
                    builder.append("'a'");
                    break;
                case "List":
                    builder.append("new ArrayList<>()");
                    if (importBuilder.indexOf("import java.util.ArrayList;") == -1) {
                        importBuilder.append("import java.util.ArrayList;\n");
                    }
                    break;
                case "Map":
                    builder.append("new HashMap<>()");
                    if (importBuilder.indexOf("import java.util.HashMap;") == -1) {
                        importBuilder.append("import java.util.HashMap;\n");
                    }
                    break;
                default:
                    builder.append("new ").append(simpleName).append("()");
            }
        }
    }

    public static void switchReturnType(String returnTypeSimpleName, String methodName) {
        switch (returnTypeSimpleName) {
            case "String":
                methodBuilder.append("assertEquals(").append("\"str\"").append(", ").append(clazzNameCamel).append(".").append(methodName).append("());\n");
                break;
            case "int":
            case "byte":
            case "short":
                methodBuilder.append("assertEquals(").append("1").append(", ").append(clazzNameCamel).append(".").append(methodName).append("());\n");
                break;
            case "long":
                methodBuilder.append("assertEquals(").append("1L").append(", ").append(clazzNameCamel).append(".").append(methodName).append("());\n");
                break;
            case "double":
            case "float":
                methodBuilder.append("assertEquals(").append("1.0").append(", ").append(clazzNameCamel).append(".").append(methodName).append("());\n");
                break;
            case "char":
                methodBuilder.append("assertEquals(").append("'a'").append(", ").append(clazzNameCamel).append(".").append(methodName).append("());\n");
                break;
            default:
                methodBuilder.append("assertNotNull(").append(clazzNameCamel).append(".").append(methodName).append("());\n");
        }
    }

    public void writeToFile() {
        String property = System.getProperty("user.dir");
        String filePath = property + "/src/test/java/" + aPackage.getName().replaceAll("\\.", "/") + "/" + clazzName + "Test.java";
        System.out.println(filePath);
        String content = packageBuilder.toString() + importBuilder.toString() + headerBuilder.toString() + fieldBuilder.toString()
                + beforeBuilder.toString() + methodBuilder.toString() + footerBuilder.toString();
        File file = new File(filePath);
        try {
            FileUtils.writeStringToFile(file, content, "UTF-8", false);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("finish");
    }
}
